#ifndef _SE_BUFFERS_
#define _SE_BUFFERS_

#include "Core/Logger.h"
#include "Core/Math.hpp"

namespace SE
{
	enum class ShaderDataType : U8
	{
		Float, Float2, Float3, Float4,
        Mat3, Mat4,
        Int, Int2, Int3, Int4,
        Bool
	};

	inline U32 GetShaderDataTypeSize(const ShaderDataType type)
	{
		switch (type)
		{
            default: SE_ASSERT(false, "Unknown ShaderDataType!"); return 0;
			case ShaderDataType::Float:    return 4;
			case ShaderDataType::Float2:   return 4 * 2;
			case ShaderDataType::Float3:   return 4 * 3;
			case ShaderDataType::Float4:   return 4 * 4;
			case ShaderDataType::Mat3:     return 4 * 3 * 3;
			case ShaderDataType::Mat4:     return 4 * 4 * 4;
			case ShaderDataType::Int:      return 4;
			case ShaderDataType::Int2:     return 4 * 2;
			case ShaderDataType::Int3:     return 4 * 3;
			case ShaderDataType::Int4:     return 4 * 4;
			case ShaderDataType::Bool:     return 1;
		}
	}

	struct BufferElement final
	{
		ShaderDataType	type;
		String			name;
		U32				size;
		U32				offset;
		Boolean			normalized;

		BufferElement() = default;
		BufferElement(
			const ShaderDataType _type,
			const String& _name,
			const Boolean _normalized = false);

		U32 getComponentCount() const;
	};

	class BufferLayout final
	{
	private:
		U32 mStride = 0;
		Vector<BufferElement> mElements;

	public:
		BufferLayout() = default;
		BufferLayout(const std::initializer_list<BufferElement>& elements);

		U32 getStride() const;
		const Vector<BufferElement>& getElements() const;

		Vector<BufferElement>::iterator begin();
		Vector<BufferElement>::iterator end();
		Vector<BufferElement>::const_iterator begin() const;
		Vector<BufferElement>::const_iterator end() const;

	private:
		void calculateOffsetsAndStride();
	};

	class VertexBuffer
	{
	public:
        VertexBuffer() = default;
		virtual ~VertexBuffer() = default;

		virtual void bind() const = 0;
		virtual void unbind() const = 0;

		virtual void setBuffer(void* buffer, const U32 size) = 0;

		virtual const BufferLayout& getLayout() const = 0;
		virtual void setLayout(const BufferLayout& layout) = 0;

		static Ref<VertexBuffer> Create(const U32 size, void* buffer = nullptr);
	};

	// Currently only supports 32-bit index buffers.
	class IndexBuffer
	{
	public:
        IndexBuffer() = default;
		virtual ~IndexBuffer() = default;

		virtual void bind() const = 0;
		virtual void unbind() const = 0;

		virtual void setBuffer(void* buffer, const U32 size) = 0;

		virtual U32 getCount() const = 0;

		static Ref<IndexBuffer> Create(const U32 count, void* buffer = nullptr);
	};

	class VertexArray
	{
	public:
		VertexArray() = default;
		virtual ~VertexArray() = default;

		virtual void bind() const = 0;
		virtual void unbind() const = 0;

		virtual void addVertexBuffer(const Ref<VertexBuffer>& vertexBuffer) = 0;
		virtual void setIndexBuffer(const Ref<IndexBuffer>& indexBuffer) = 0;

		virtual const Vector< Ref<VertexBuffer> >& getVertexBuffers() const = 0;
		virtual const Ref<IndexBuffer>& getIndexBuffer() const = 0;

		static Ref<VertexArray> Create();
	};

	static constexpr U32 MaxBufferSize = 8192;

	// Only contains ONE native buffer inside.
	class Buffer
	{
	public:
		Buffer() = default;
		virtual ~Buffer() = default;

		virtual U32	getRenderID() const = 0;
		virtual U32	getDimension() const = 0;
		virtual U32	getBufferSize(const U32 dimension) const = 0;

		virtual void setBuffer(void* buffer, U32* size, U32 dimension) = 0;
	};

	static constexpr U32 MaxTextureSlots = 32;

	enum class RenderInternalFormat : U8
	{
		None,
		R8,
		RG8,
		RGB8,
		RGBA8,
		RGBA32F,
		Depth32F,
		Stencil32F,
		Depth24FStencil8F
	};
	enum class RenderDataFormat : U8
	{
		None,
		R,
		RG,
		RGB,
		RGBA,
		Depth,
		Stencil,
		DepthAndStencil
	};

	class TextureBuffer : public Buffer
	{
	public:
		enum class WrapMode : U8
		{
			None, Repeat, MirroredRepeat
		};
		enum class MinFilterMode : U8
		{
			None, Linear, Nearest
		};
		enum class MagFilterMode : U8
		{
			None, Linear, Nearest
		};

		TextureBuffer() = default;
		virtual ~TextureBuffer() = default;

		virtual void bind(const U32 slot) const = 0;

		static Ref<TextureBuffer> Create(
			const U32					dimension,
			const U32*					size,
			const RenderInternalFormat	format,
			const WrapMode				wrapMode,
			const MinFilterMode			minFilterMode,
			const MagFilterMode			magFilterMode,
			const Boolean				genMipmaps,
			const Boolean				multisample);
	};

	class RenderBuffer : public Buffer
	{
	public:
		RenderBuffer() = default;
		virtual ~RenderBuffer() = default;

		virtual void bind() = 0;

		static Ref<RenderBuffer> Create(
			const U32					dimension,
			const U32*					size,
			const RenderInternalFormat	format);
	};

	// The buffers contained are all TWO dimension.
	class FrameBuffer
	{
	public:
		// Only support 1 color slots.
		enum class AttachmentType : U8
		{
			Color = 0, Depth = 1, Stencil = 2, DepthAndStencil = 3
		};

		enum class AttachmentBufferType : U8
		{
			Texture = 0, RenderBufferObject = 1
		};

		struct AttachmentLayoutElement final
		{
			AttachmentType			attachmentType;
			AttachmentBufferType	attachmentBufferType;
			RenderInternalFormat	internalFormat;
			RenderDataFormat		dataFormat;
			Ref<Buffer>				buffer;

			AttachmentLayoutElement(
				const AttachmentType		_attachmentType,
				const AttachmentBufferType	_attachmentBufferType,
				const RenderInternalFormat	_internalFormat,
				const RenderDataFormat		_dataFormat)
				: attachmentType(_attachmentType),
				  attachmentBufferType(_attachmentBufferType),
				  internalFormat(_internalFormat),
				  dataFormat(_dataFormat),
				  buffer(nullptr) {}
			~AttachmentLayoutElement() = default;
		};

	public:
		FrameBuffer() = default;
		virtual ~FrameBuffer() = default;

		virtual void bind() const = 0;
		virtual void unbind() const = 0;

		virtual U32 getWidth() const = 0;
		virtual U32 getHeight() const = 0;
		virtual U32 getAttachmentID(const AttachmentType type) const = 0;

		virtual void setBuffer(const U32 width, const U32 height) = 0;

		static Ref<FrameBuffer> Create(const std::initializer_list<AttachmentLayoutElement>& layout);
	};
} // !namespace SE

#endif // !_SE_BUFFERS_